package logic;

import gui.DefeatScreen;
import gui.VictoryScreen;

import java.io.IOException;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;

/**
 * class for sending data through bluetooth stream
 * @author Rekin
 *
 */
/**
 * @author Rekin
 *
 */
public class BluetoothCommunicationHandler {
	
	private BluetoothConnection[] connections;
	private boolean isServer;
	private EngineBluetoothGame engineData;
	private boolean connectionWorking;
	private ReceiverThread receiver;
	
	/**
	 * creates handler for connection for a given connections and set it as client or server handler
	 * @param connections list of connections that this handler handles
	 * @param isServer determines if this handler is on client or server side
	 */
	public BluetoothCommunicationHandler(BluetoothConnection[] connections, 
			boolean isServer){
		this.connections=connections;
		this.isServer=isServer;
		connectionWorking=true;
	}
	
	/**
	 * sets the reference to engine class object
	 * @param e given engine - type multiplayer
	 */
	public void setEngine(EngineBluetoothGame e){
		engineData=e;
	}
	
	/**
	 * starts receiving thread to read and write messages
	 */
	public void startRetrieving(){
			receiver = new ReceiverThread();
			receiver.start();
	}
	
	/**
	 * @return if is server handler
	 */
	public boolean isServer() {
		return isServer;
	}
	
	/**
	 * @return if bluetooth connection is working correctly
	 */
	public boolean isConnectionWorking() {
		return connectionWorking;
	}
	
	/**
	 * closes connections with others
	 */
	public void closeConnections(){
		try{
		connectionWorking=false;
		connections[0].close();
		}
		catch(IOException e){}
	}
	
	/**
	 * sends a message to another device
	 * @param message message to be send to remote device
	 * @return true if successfully sent
	 */
	public boolean sendMessage(String message){
		String preparedMessage = message;
		try {
			connections[0].writeString(preparedMessage);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	/**
	 * server sends cards to remote device
	 */
	public void sendCardsToOtherPlayerCom(){
		try {
			receiver.sendCardsToClient();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * client sends selection of cards to remote server
	 */
	public void sendSelectedClientCards(){
		try {
			receiver.sendSelectedCardsIndexesToServer();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * client prepares received cards for next round, but doesn't propagate them to engine 
	 * @param playerCardsForNextRound bundle of given cards
	 */
	public void setPlayerCardsForNextRound(String playerCardsForNextRound) {
		receiver.playerCardsForNextRound = playerCardsForNextRound;
	}

	/**
	 * @return received before cards for next round
	 */
	public String getPlayerCardsForNextRound() {
		return receiver.getPlayerCardsForNextRound();
	}
	
	/**
	 * propagates given card data from remote server into it's engine
	 * @param cardsData cards data receiver before
	 * @param player player whose cards will be changed in the engine
	 */
	public void setPlayerCardsFromRemoteData(String cardsData, Player player){
		receiver.setPlayerCardsFromRemoteData(cardsData, player);
	}

	/**
	 * receiver thread for receiving data messages from a remote device
	 * @author Rekin
	 *
	 */
	/**
	 * @author Rekin
	 *
	 */
	private class ReceiverThread extends Thread{
		private BluetoothConnection bc;
		private String playerCardsForNextRound;
		
		/**
		 * default constructor 
		 */
		public ReceiverThread() {
			bc=connections[0]; // we have point to point connection
		}

		/**
		 * @return cards prepared for next round
		 */
		public String getPlayerCardsForNextRound() {
			return playerCardsForNextRound;
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		public void run() {
			try {
				String strName; //name of second player
				int flag=-1; //value to store message type
				String message;
				// read other player name
				strName = bc.readString();
				engineData.saveReceivedPlayerData(strName);
				
				if(!isServer){
					receiveStartingDataFromServer();
					MainMidlet.getMidletInstance().showMultiplayerGame(); //now client can start multiplayer game
				}
				else{
					sendServerGameStartingDataToClient();
					MainMidlet.getMidletInstance().showMultiplayerGame(); //now server can start multiplayer game
				}
				// now start retrieving flags and information from other player
				while (connectionWorking) {
					message = bc.readString();
					flag = parseMessageFlag(message);
					
					switch (flag) {
						case NetMessage.ACTION_BUTTON: 
							engineData.doReceiveLogic(Integer.parseInt(parseMessageData(message)));
							break;
						case NetMessage.ACTION_FINAL: 
							int w = Integer.parseInt(parseMessageData(message));
							if(w==0){ //server won
								MainMidlet.getMidletInstance().setDisplay(new DefeatScreen());
							}
							else if(w==1){ //client won
								MainMidlet.getMidletInstance().setDisplay(new VictoryScreen());
							}
							break;
						case NetMessage.ACCEPT_DOUBLE: 
							engineData.getPlayer(1).substractMoney(Engine.defaultStake);
							engineData.setMoneyOnBoard(engineData.getMoneyOnBoard()+Engine.defaultStake);
							MainMidlet.getMidletInstance().getBoard().doMultiplayerRevalidate();
							//BluetoothCommunicationHandler.this.sendMessage(""+NetMessage.ACTION_BUTTON+NetMessage.SEPARATOR+NetMessage.TOKEN);
							break;
						case NetMessage.CARDS: //cards for client after exchange
								String cards = parseMessageData(message);
								System.out.println("CARDS FOR CLIENT >>>\n"+cards);
								if(engineData.getCurrentRound()!=4){
									setPlayerCardsFromRemoteData(cards, engineData.getPlayer(0));
									for(int i=0;i<5;++i){
										engineData.getPlayer(0).getCard(i).setSelected(false);
									}
									int ps=-1;
									if((ps=cards.indexOf("<>"))!=-1){
										setPlayerCardsFromRemoteData(cards.substring(ps+2), engineData.getPlayer(1));
									}
									//refresh cards but don't allow to move yet
									MainMidlet.getMidletInstance().getBoard().resetCardsToStartingPosition();
									MainMidlet.getMidletInstance().getBoard().newSpritesImages();
								}
								if(engineData.getCurrentRound()==4){
									playerCardsForNextRound=cards;
									engineData.setHasToken(true);
								}
								MainMidlet.getMidletInstance().getBoard().doMultiplayerRevalidate();
							break;
						case NetMessage.SELECTED_CARDS: //server reads cards selection
							String cardsSel = parseMessageData(message);
							for(int i=0;i<5;++i){ //set cards selection
								if(cardsSel.charAt(i)=='1'){
									engineData.getPlayer(1).getCard(i).setSelected(true);
								}
							}
							//exchange cards
							engineData.getPlayer(1).exchangeCards(engineData.getCardDeck());
							sendCardsToClient(true); //send new cards to client and server cards as well
							engineData.setHasToken(true);
							MainMidlet.getMidletInstance().getBoard().doMultiplayerRevalidate();
							break;
						case NetMessage.SERVER_WHO_WON:
							String winnerData = parseMessageData(message);
							if(winnerData.charAt(0)=='1'){ //client won
								engineData.setRoundWinner(0);
								engineData.setWinnerName(engineData.getPlayer(0).getName());
							}
							else{	//client lost
								engineData.setRoundWinner(1);
								engineData.setWinnerName(engineData.getPlayer(1).getName());
							}
							engineData.getPlayer(1).setSeqTxt(winnerData.substring(2));
							engineData.getPlayer(0).cardSort();
							engineData.getPlayer(0).countCards();
							MainMidlet.getMidletInstance().getBoard().doMultiplayerRevalidate();
							break;
						case -1: //system flag when stream is finished (another device disconnected)
							connectionWorking=false;
							break;
					default:
						break;
					}
				}
				//we have lost connection, so we close our streams, and go to main menu showing error
				bc.close();
				MainMidlet.getMidletInstance().showMainMenu();
				Alert al=LogicHelper.makePopup("Error", "Bluetooth connection lost", -1, AlertType.ERROR);
				MainMidlet.getMidletInstance().setDisplay(al);
			}
			catch (IOException e) {
				System.out.println("bt receive exception (IOException): "+e.getMessage());
				MainMidlet.getMidletInstance().showMainMenu();
				Alert al=LogicHelper.makePopup("Error", "Bluetooth connection lost", -1, AlertType.ERROR);
				MainMidlet.getMidletInstance().setDisplay(al);
			}
			catch (NegativeArraySizeException e){
				System.out.println("bt receive exception(NegativeArraySizeException): "+e.getMessage());
				MainMidlet.getMidletInstance().showMainMenu();
				Alert al=LogicHelper.makePopup("Error", "Bluetooth connection lost", -1, AlertType.ERROR);
				MainMidlet.getMidletInstance().setDisplay(al);
			}
			catch (Exception e){
				System.out.println("bt receive exception(Exception): "+e.getMessage());
				MainMidlet.getMidletInstance().showMainMenu();
				Alert al=LogicHelper.makePopup("Error", "Bluetooth connection lost", -1, AlertType.ERROR);
				MainMidlet.getMidletInstance().setDisplay(al);
			}
		}//run
		
		
		/**
		 * method sends cards to remote client
		 * @throws IOException 
		 */
		private void sendCardsToClient() throws IOException{
			sendCardsToClient(false);
		}
		
		/**
		 * method sends cards to remote player - client via bluetooth
		 * @param serverCardsAlso indicates if server cards should also be sent
		 * @throws IOException 
		 */
		private void sendCardsToClient(boolean serverCardsAlso) throws IOException{
			StringBuffer buf = new StringBuffer();
			buf.append(""+NetMessage.CARDS+NetMessage.SEPARATOR);
			for(int i=0;i<5;++i){
				buf.append(""+engineData.getPlayer(1).getCard(i).getColor()+
						"_"+engineData.getPlayer(1).getCard(i).getFigure()+"x");
			}
			if(serverCardsAlso){
				buf.append("<>");
				for(int i=0;i<5;++i){
					buf.append(""+engineData.getPlayer(0).getCard(i).getColor()+
							"_"+engineData.getPlayer(0).getCard(i).getFigure()+"x");
				}
			}
			System.out.println("SENDING CARDS TO CLIENT...\n"+buf.toString());
			bc.writeString(buf.toString());
		}
		
		/**
		 * method for sending client's selected cards to server
		 * @throws IOException
		 */
		private void sendSelectedCardsIndexesToServer() throws IOException{
			StringBuffer buf = new StringBuffer();
			buf.append(""+NetMessage.SELECTED_CARDS+NetMessage.SEPARATOR);
			int sel=0;
			for(int i=0;i<5;++i){
				if(engineData.getPlayer(0).getCard(i).isSelected()){
					sel=1;
				}
				else{
					sel=0;
				}
				buf.append(sel);
			}
			bc.writeString(buf.toString());
		}
		
		/**
		 * extracts only data from a given message
		 * @param mess whole received message
		 * @return extracted data from message
		 */
		private String parseMessageData(String mess){
			int flagEnd = mess.indexOf(NetMessage.SEPARATOR);
			return mess.substring(flagEnd+1);
		}
		/**
		 * extracts only message flag from a given message
		 * @param mess whole received message
		 * @return extracted flag from message
		 */
		private int parseMessageFlag(String mess){
			int flagEnd = mess.indexOf(NetMessage.SEPARATOR);
			String subm = mess.substring(0,flagEnd);
			return Integer.parseInt(subm);
		}
		
		/**
		 * server sends starting data to client
		 * @throws IOException
		 */
		private void sendServerGameStartingDataToClient() throws IOException{
			sendStartingMoneyToClient();
			sendCardsToClient();
		}
		
		/**
		 * server sends starting money to client
		 * @throws IOException
		 */
		private void sendStartingMoneyToClient() throws IOException{
			bc.writeString(""+NetMessage.STARTING_MONEY+NetMessage.SEPARATOR+engineData.getStartingMoney());
		}
		
		/**
		 * method for receiving bundle of starting data from server
		 * @throws IOException
		 */
		private void receiveStartingDataFromServer() throws IOException{
			//receive data and set engine variables
			String message = bc.readString(); // starting money
			int money = Integer.parseInt(parseMessageData(message));
			engineData.getPlayer(0).setMoney(money);
			engineData.getPlayer(1).setMoney(money);
			message = bc.readString(); // cards message
			String cardsData = parseMessageData(message);
			setPlayerCardsFromRemoteData(cardsData, engineData.getPlayer(0));
		}
		
		/**
		 * method for setting player cards in engine from a given card data that was received previously
		 * @param cardsData card data
		 * @param player player which cards will be changed
		 */
		public void setPlayerCardsFromRemoteData(String cardsData, Player player){
			int startIterator = 0;
			int endIterator = 0;
			String[] singleCard = new String[5];
			for(int i=0;i<5;++i){
				endIterator = cardsData.indexOf("x",startIterator+1);
				singleCard[i]=cardsData.substring(startIterator, endIterator);
				startIterator = endIterator+1;
			}
			for(int i=0;i<5;++i){
				int col = Integer.parseInt( singleCard[i].substring(0, 1) );
				int fig = Integer.parseInt( singleCard[i].substring(2) );
				player.setCard(i, col, fig);
			}
		}
		
	}//class Retreiver
}//class Handler
